Skip to content

feat(tests/screencast): add Python/Playwright screencast pipeline + hello_world demo scenario#3042

Open
mettta wants to merge 39 commits into
mainfrom
mettta/screencast
Open

feat(tests/screencast): add Python/Playwright screencast pipeline + hello_world demo scenario#3042
mettta wants to merge 39 commits into
mainfrom
mettta/screencast

Conversation

@mettta

@mettta mettta commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator
  • Adds a self-contained demo-video pipeline in Python/Playwright: each scenario is a dual-purpose pytest test (invoke test-screencast) that also (re)generates its .webm via --record-video, backed by a real live SDocTestServer instance and a small Playwright Page Object layer (tests/screencast/helpers/).
  • Adds the hello_world scenario: a real strictdoc new run (shown in a terminal-style scene) → browsing the generated project in the web UI → following a Requirement's Parent/Child relation links across documents → editing an existing Requirement to add a Rationale → an editor-style scene revealing the real resulting .sdoc diff, syntax-highlighted with StrictDoc's own StrictDocLexer.
  • Adds demo-quality infrastructure reused by every scene: Pointer (a fake cursor + click-target highlight, since Playwright renders no real cursor), pacing helpers for deliberate pauses, terminal/editor/IDE-typing HTML playgrounds, and web-optimized video export.
  • Adds invoke screencast-server for manually previewing a scenario in a real browser — defaults to a disposable, read-only copy of the project (nothing persists, the shared fixture is never touched); --edit opts into the real, persistent files when intentionally curating them.
  • Adds README.md documenting the whole mechanism: commands, pacing/cursor knobs, scene-editing, the read-only/--edit manual server, syntax highlighting.

mettta added 30 commits July 13, 2026 20:34
tests/screencast/ was a raw copy of the demo-video generator from the
strictdoc-project.github.io (Hugo) repository. This is the first step of
moving that generator into the StrictDoc repo itself (see
developer/tasks/20260704_screencast/task.md).

Removed as part of this cleanup:
- demo.js, package.json, package-lock.json, node_modules/: the JS/Playwright
  pipeline is being rewritten in Python (Playwright has a full Python API),
  so StrictDoc gains no new Node.js/npm dependency.
- run_demo.py: only orchestrated the now-removed `npm run demo` step.
- fixtures/strictdoc-demo-project/.local/: personal working notes with
  another machine's absolute paths and stray duplicate .sdoc files, not
  part of the actual demo fixture.
- __pycache__/, .DS_Store, and the generated fixtures/.../output/ cache:
  build artifacts, not source.

Still present, to be replaced in a later stage: run_server.py and
strictdoc_server.py, which currently start StrictDoc by shelling out to
`tox` against a sibling repo checkout — an assumption that only made sense
outside the StrictDoc repo. These will be replaced by reusing
tests/end2end/server.py::SDocTestServer.

No functional change to StrictDoc itself; tests/screencast is not yet wired
into any invoke task.
Adds the `playwright` Python package to requirements.check.txt, alongside
seleniumbase, since it is installed and used through the same tox `check`
environment as the end2end test suite.

This is the dependency needed for the tests/screencast video-generation
pipeline being rewritten in Python (see
developer/tasks/20260704_screencast/task.md). The pipeline code itself and
the one-time `playwright install chromium` browser-binary setup step will
follow in later stages of that task.
Replaces the ad-hoc strictdoc_server.py (which shelled out to `tox -e
py310-development` against a sibling `../strictdoc` checkout — an
assumption that only made sense when this generator lived in the
strictdoc-project.github.io repo) with the already-hardened
tests/end2end/server.py::SDocTestServer, used as-is via its context-manager
interface.

Adds tests/screencast/fixture.py with the shared fixture-project and port
constants (a separate RECORD_SERVER_PORT is reserved for the upcoming
video-recording mode, so a manual dev server can stay open while a scenario
re-records).

run_server.py is rewritten to start/stop the demo server through
SDocTestServer directly — no tox subprocess, no sibling-checkout
assumption, no STRICTDOC_ROOT/STRICTDOC_TOX/pyenv-version env vars.

Manually verified: the server starts against the demo fixture, serves
HTTP 200, and shuts down cleanly on exit.
…st tests

Replaces the JSON-step scene definitions in the removed demo.js with two
ordinary pytest test cases, following the tests/end2end/screens/*/test_case.py
convention: each screencast scenario is now a real regression check, not
just a replayed sequence of UI actions.

- strictdoc_ui/test_case.py: browse a StrictDoc document, switch to the
  table view, and assert on document title, viewtype menu, and the
  resulting table view state.
- ide_typing_to_table/test_case.py: the IDE-style fake-typing scene
  (demo.html) followed by the same table-view flow.
- scenarios/conftest.py: a local `page` pytest fixture (headless Chromium
  via playwright.sync_api directly — no pytest-playwright plugin needed for
  this scope).
- scenarios/typing.py: the fake-typing effect ported from demo.js's
  typeText() (appends to textContent; not real keyboard input).

Note: tests/end2end's Screen_*/component Page Object helpers are tied to
SeleniumBase (BaseCase, By.XPATH) and cannot be reused as-is by Playwright
code. The same underlying selectors (#viewtype_handler,
[data-viewtype_link="table"], body[data-viewtype], .header__document_title)
are used directly instead, verified against the actually rendered demo
fixture pages.

Both scenarios currently run without recording video; recording mode is
added in a follow-up stage.

Manually verified: both tests pass under the `check` tox environment
(playwright + chromium installed) against the demo fixture server.
…st tests

Replaces the JSON-step scene definitions in the removed demo.js with two
ordinary pytest test cases, following the tests/end2end/screens/*/test_case.py
convention: each screencast scenario is now a real regression check, not
just a replayed sequence of UI actions.

- strictdoc_ui/test_case.py: browse a StrictDoc document, switch to the
  table view, and assert on document title, viewtype menu, and the
  resulting table view state.
- ide_typing_to_table/test_case.py: the IDE-style fake-typing scene
  (demo.html) followed by the same table-view flow.
- scenarios/conftest.py: a local `page` pytest fixture (headless Chromium
  via playwright.sync_api directly — no pytest-playwright plugin needed for
  this scope).
- scenarios/typing.py: the fake-typing effect ported from demo.js's
  typeText() (appends to textContent; not real keyboard input).

tests/end2end's Screen_*/component Page Object helpers are tied to
SeleniumBase (BaseCase, By.XPATH) and cannot be called from Playwright
code. Instead of inlining locators per test, this adds new Playwright-native
helpers under tests/screencast/helpers/ (Screen, ViewTypeSelector),
mirroring the existing helpers' names, selectors, and responsibilities —
meant for reuse across this suite now and as a starting point for a wider
Playwright migration later, per SDG guidance to avoid one-off duplication
once more than one call site exists.

Both scenarios currently run without recording video; recording mode is
added in a follow-up stage.

Manually verified: both tests pass under the `check` tox environment
(playwright + chromium installed) against the demo fixture server.
Makes each screencast scenario dual-purpose, as designed in
developer/tasks/20260704_screencast/task.md: by default it's a fast
pass/fail regression check with no video output; with
--strictdoc-record-video, the same test also captures and saves a .webm.

- fixture.py: add OUTPUT_DIR (tests/screencast/output/).
- scenarios/conftest.py: add the --strictdoc-record-video pytest option;
  the `page` fixture enables Playwright's record_video_dir only when the
  flag is set, and on context close saves the recording as
  output/<scenario-dir-name>.webm (e.g. strictdoc_ui.webm), removing
  Playwright's temporary video file afterwards.

Removed the two leftover .webm files from the old JS pipeline
(strictdoc-ui.webm, ide-to-table.webm) — different naming scheme, no
longer produced by this generator.

Manually verified: scenarios pass both without the flag (no video written)
and with it (both named .webm files produced, no stray temp files left in
output/).
Adds `invoke test-screencast` (alias `tsc`) in tasks.py, following the same
tox `check`-environment convention as test_end2end/test_unit_server
(junit-xml under build/test_reports/, dedicated pytest cache dir).

Options:
- --focus: pytest -k filter, same as other test-* tasks.
- --record-video: forwards --strictdoc-record-video to pytest, so the same
  invocation both runs the regression checks and (re)generates the .webm
  videos into tests/screencast/output/.

Kept separate from invoke test-end2end per
developer/tasks/20260704_screencast/task.md: screencast runs are
slower/produce binary artifacts, and the scenarios always run headless (no
headed/--headless flag needed, unlike SeleniumBase-based end2end tests).

Manually verified: `invoke test-screencast` and
`invoke test-screencast --record-video` both pass and behave as expected
(2 passed; the latter produces strictdoc_ui.webm and
ide_typing_to_table.webm in tests/screencast/output/).
…line

Replaces the README carried over from strictdoc-project.github.io (which
documented `npm install --prefix demo`, STRICTDOC_ROOT/STRICTDOC_TOX env
vars, and a sibling-checkout setup) with docs matching the pipeline as it
now exists in this repo: directory layout, the one-time
`playwright install chromium` setup step, `invoke test-screencast`
(with --focus/--record-video), the manual dev server via run_server.py,
and a short guide for adding new scenarios using the helpers/ Page Objects.

A failing scenario means its video can no longer be trusted, not that it
should be re-recorded automatically: investigate first (it may be a product
bug or an intentional UI change the scenario doesn't reflect yet), and
re-record once the scenario is fixed or updated and passing again.

Also fixes fixtures/strictdoc-demo-project/README.md, which still referred
to a `demo/` directory that no longer exists.

Manually verified: `invoke test-screencast` passes (2 passed) as a final
end-to-end sanity check of the whole rewritten pipeline.
…plate

- Rewrites WHAT/WHY/HOW to match developer/tasks/task.template.md
- Removes narrative that didn't belong in a task specification
…ctions

run_server.py imports project dependencies (psutil, watchdog via
strictdoc itself) that only exist in this project's tox environments, not
in a bare system `python3`. Running it directly with `python3
tests/screencast/run_server.py` fails with `ModuleNotFoundError: No module
named 'watchdog'`.

Adds `invoke screencast-server` (alias `scs`), following the same pattern
as the existing `invoke server` task: runs run_server.py through the `check`
tox environment (the one with psutil/playwright/etc., matching
`invoke test-screencast`), so it has the dependencies it needs.

Updates tests/screencast/README.md to document `invoke screencast-server`
instead of the bare `python3` invocation.

Manually verified: `invoke screencast-server` starts the demo server and
serves HTTP 200 against the fixture project; `invoke test-screencast`
still passes end-to-end after these changes.
…ults

DEV_SERVER_PORT was 5111 — StrictDoc's own default project server port
(ProjectConfigDefault.DEFAULT_SERVER_PORT), the same one `invoke server`
uses. Running `invoke screencast-server` and `invoke server` at the same
time (or leaving one running) collides on that port; this is what caused
`invoke server`'s "Address already in use" failure.

RECORD_SERVER_PORT (5112) had the same problem one level down: it's
tests/end2end's SDocTestServer default fallback port, used by any end2end
test that doesn't pass an explicit port.

Moves both ports to 5301/5302, clear of StrictDoc's own default (5111) and
the end2end port range (5112-5212). Updates tests/screencast/README.md's
port references to match.

Manually verified: `invoke test-screencast` (2 passed, server on 5302) and
`invoke screencast-server` (serves HTTP 200 on 5301) both work on the new
ports, and leave 5111 untouched.
…-screencast

Adds .github/workflows/screencast-tests.yml, separate from
end2end-tests.yml: sets up the `check` tox environment, installs
Playwright's Chromium binary (not covered by pip install), then runs
`invoke test-screencast`. Without this, the WHY in
developer/tasks/20260704_screencast/task.md ("ties video freshness
directly to the same CI/dev signal already used for UI regressions") was
aspirational rather than true — nothing ran these scenarios automatically.

Also fixes lint issues in tests/screencast/run_server.py surfaced by
`invoke lint`: dropped the unused shebang (the script is only ever invoked
via `python tests/screencast/run_server.py`, never executed directly),
switched `List[...]` to `list[...]`, and added `# noqa: T201` to its
prints, matching the rest of the codebase's convention for intentional
console output.

Manually verified: each workflow step run locally in sequence (tox
--notest, pip_install_strictdoc_deps.py, playwright install chromium,
invoke test-screencast) succeeds; `ruff check tests/screencast/` is clean.
- `invoke screencast-server` gains `--focus=<scenario>` to manually
  preview scenarios that don't use the shared fixture project (like
  hello_world, which generates its own project on demand into the
  gitignored build/screencast_manual/<scenario>/ and reuses it across
  restarts).
- New `hello_world` screencast scenario: runs a real `strictdoc new`,
  types the command and its actual stdout into a terminal-style HTML
  scene, then drives a live server through creating a document and a
  requirement via the web UI (tests/screencast/scenarios/hello_world/).
- New Playwright helpers ported from the e2e SeleniumBase suite for the
  add-document and add-requirement flows (tests/screencast/helpers/).
Adds Pointer (fake mouse cursor + click-target highlight, since
Playwright renders no real cursor) and pause() pacing helper; reworks
the hello_world scenario to skip document creation, add a Requirement
to the existing High-Level Requirements doc instead, and close with an
editor-style scene revealing the real appended .sdoc lines.
Moves the cursor/highlight CSS out of the inline JS template string in
pointer.py into pointer.css, embedded via json.dumps() instead of
repr() so the CSS can safely contain quotes/backslashes without
breaking the generated JS.
…DocLexer

Reuses StrictDoc's own Pygments lexer and pygments.css (same ones used
to highlight `.. code:: strictdoc` blocks in the docs) to render the
.sdoc source in the editor scene, instead of plain unhighlighted text.
…syntax highlighting in README

Adds coverage in tests/screencast/README.md for the helpers table, video resolution (VIEWPORT_SIZE), pacing/pause knobs, the fake cursor/highlight mechanism (pointer.py/pointer.css), editing terminal/editor scene styles, and the real StrictDocLexer-based syntax highlighting — none of which was documented after the recent Pointer/hello_world work.
…dd --edit and a startup banner

`invoke screencast-server` now defaults to a fresh, disposable copy of
the scenario's project (nothing persists, the shared fixture is never
touched); pass --edit to serve the real, persistent files instead.
Replaces the plain startup print with a boxed banner (matching
StrictDoc's own server banner style) showing scenario/mode/URL, with
display-width-aware padding so wide characters don't misalign the frame.
…6/UP037/UP045)

Replaces typing.List/Dict/Tuple/Optional/Union with list/dict/tuple/X | None/X | Y in manual_scenarios.py, helpers/editor_scene.py, and helpers/pointer.py (UP006/UP045, no safe autofix available); also picks up ruff --fix's automatic removal of a now-redundant quoted forward-reference in helpers/node.py's Requirement.with_node return type (UP037), safe to auto-fix since from __future__ import annotations is already in effect there.
…st pipeline

Add web-optimized video export to the screencast pipeline

Recorded tests/screencast/output/*.webm files are VP8, sized for
editing, not for embedding on the product website. Add `invoke
screencast-optimize-video` (alias `scov`), which re-encodes each
recording into a muted tests/screencast/output/web/<scenario>.webm
(VP9) + <scenario>.mp4 (H.264, for Safari/older browsers) pair,
capped at 1280px wide without upscaling narrower recordings.

Resolves ffmpeg via PATH with a clear error if it's missing.
…nstead of adding one

Switches the scenario to open Low-Level Requirements and add a Rationale
to the existing LLR-1 (was: creating a new HLR-2 requirement).
editor_scene.reveal_change() now diff-inserts changed lines at their real
position in the file instead of only appending at the end. Terminal/demo
scenes get proper flex+overflow scrolling so typed/appended text
auto-scrolls into view.
…ve path in `strictdoc new` output

Pointer's fake cursor starts hidden (opacity:0), appearing only on first
move/click — scenes with no clicks (terminal/editor) no longer show it
frozen in a corner. run_strictdoc_new now passes a relative path + cwd
so the command's own printed "Location"/"cd" output stays short instead
of leaking pytest's absolute tmp_path.
…video

Navigates to the terminal scene before running the real `strictdoc new`
subprocess — video recording starts as soon as the browser context
exists, so running the subprocess first left about:blank white on
screen for the first second of the recording.
mettta added 9 commits July 15, 2026 00:03
…orld

Adds Node.do_click_parent_relation()/do_click_child_relation()
(StrictDoc renders these as plain a.requirement__link-parent/child, no
data-testid) and a new scene: LLR-1 → its Parent (cross-document
navigation to HLR-1) → back via the Child relation, before editing.
… narrow viewports

min-width: 0 override for the flexbox min-width:auto default, which was
making width: 99% ineffective at narrow widths (flexbug).
…eal fix

Commented out, not deleted — this margin exists so the last anchor in a
document can scroll fully into view, but also causes an unwanted
jump-to-bottom when following any fragment link on a short page.
Disabling here rather than fixing properly (a real fix needs the margin
to apply conditionally, e.g. via a JS-toggled class, not pure CSS).
…nale

Waits for the just-opened edit form's height to stabilize (it keeps
growing briefly right after appearing) before scrolling .main to the
bottom — otherwise the scroll undershoots against still-growing content.
@mettta
mettta requested a review from stanislaw July 14, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant